package com.jetbrains.edu.csharp.refactoring

import com.intellij.openapi.application.invokeAndWaitIfNeeded
import com.intellij.openapi.application.runWriteAction
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiReference
import com.jetbrains.edu.coursecreator.handlers.StudyItemRefactoringHandler
import com.jetbrains.edu.csharp.CSharpConfigurator
import com.jetbrains.edu.csharp.formatForCSProj
import com.jetbrains.edu.csharp.getCSProjFileName
import com.jetbrains.edu.learning.courseDir
import com.jetbrains.edu.learning.courseFormat.Lesson
import com.jetbrains.edu.learning.courseFormat.Section
import com.jetbrains.edu.learning.courseFormat.StudyItem
import com.jetbrains.edu.learning.courseFormat.ext.getDir
import com.jetbrains.edu.learning.courseFormat.ext.getVirtualFile
import com.jetbrains.edu.learning.courseFormat.tasks.Task
import com.jetbrains.rider.ideaInterop.fileTypes.msbuild.CsprojFileType
import java.nio.file.Path
import kotlin.io.path.pathString
import kotlin.io.path.relativeTo

class CSharpStudyItemRefactoringHandler : StudyItemRefactoringHandler {
  override fun beforeMoveLesson(project: Project, lesson: Lesson, newParent: VirtualFile) {
    val tasks = lesson.taskList
    tasks.forEach { task ->
      val oldCSProjFileName = task.getCSProjFileName()
      val newCSProjFileName = constructNewCSProjFileNameOnMove(
        task.name, newParent.toNioPath().resolve(lesson.name), project.courseDir.toNioPath()
      )
      renameCSProj(project, task, oldCSProjFileName, newCSProjFileName)
    }
  }

  override fun beforeMoveTask(project: Project, task: Task, newParent: VirtualFile) {
    val oldCSProjFileName = task.getCSProjFileName()
    val newCSProjFileName = constructNewCSProjFileNameOnMove(task.name, newParent.toNioPath(), project.courseDir.toNioPath())
    renameCSProj(project, task, oldCSProjFileName, newCSProjFileName)
  }

  private fun renameCSProj(
    project: Project,
    task: Task,
    oldCSProjFileName: String,
    newCSProjFileName: String,
  ) {
    val taskVirtualFile = task.getTaskFile(oldCSProjFileName)?.getVirtualFile(project) ?: return

    // rename the csproj file in the file system and delete
    // generated directories, as they will be regenerated by Rider
    val taskDir = task.getDir(project.courseDir) ?: error("Task directory for task ${task.name} not found")
    // in theory these directories may be absent
    val generatedDirs = listOfNotNull(
      taskDir.findChild(CSharpConfigurator.BIN_DIRECTORY),
      taskDir.findChild(CSharpConfigurator.OBJ_DIRECTORY)
    )
    invokeAndWaitIfNeeded {
      runWriteAction {
        for (dir in generatedDirs) {
          dir.delete(this)
        }
        taskVirtualFile.rename(this, newCSProjFileName)
      }
    }
  }

  private fun constructNewCSProjFileNameOnMove(taskName: String, newParent: Path, baseDir: Path): String {
    val taskPartOfTheCSProjName = taskName.formatForCSProj()
    val relativePath = newParent.relativeTo(baseDir).pathString.formatForCSProj()
    return "$relativePath.$taskPartOfTheCSProjName.${CsprojFileType.defaultExtension}"
  }

  override fun beforeRenameStudyItem(project: Project, item: StudyItem, newName: String) {
    val tasks = item.getTasks()
    tasks.forEach { task ->
      val oldCSProjFileName = task.getCSProjFileName()
      val newCSProjFileName = oldCSProjFileName
        .split(".")
        .reversed().mapIndexed { ind, it -> if (ind == item.getDepth()) newName.formatForCSProj() else it }
        .reversed().joinToString(".")
      renameCSProj(project, task, oldCSProjFileName, newCSProjFileName)
    }
  }

  private fun StudyItem.getDepth() = when (this) {
    is Section -> 3
    is Lesson -> 2
    is Task -> 1
    else -> -1
  }

  private fun StudyItem.getTasks() = when (this) {
    is Section -> lessons.flatMap { it.taskList }
    is Lesson -> taskList
    is Task -> listOf(this)
    else -> emptyList()
  }

  override fun processReferences(references: MutableCollection<PsiReference>): MutableCollection<PsiReference> {
    // is needed to avoid double refactoring of yaml files
    return references.filter { it.element.containingFile.virtualFile.extension != "yaml" }.toMutableList()
  }
}
